#pragma once
#include <string>
#include <mutex>
#include <vector>
#include <assert.h>
#include <fstream>
#include <jsoncpp/json/json.h>
//#include "oj_model.hpp"
#include "oj_model2.hpp"
#include "oj_view.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include "../comm/httplib.h"

using namespace httplib;
// 提供服务的主机
class Machine
{
public:
    std::string _ip;    //ip
    int _port;          //端口
    uint64_t _load;     //负载
    std::mutex* _mtx;   //锁，禁止拷贝
public:
    Machine():_ip(""), _port(0), _load(0), _mtx(nullptr)
    {}
    ~Machine()
    {}

    // 增加主机负载
    void IncLoad()
    {
        if (_mtx)
            _mtx->lock();

        _load++;

        if (_mtx)
            _mtx->unlock();
    }

    // 减少主机负载
    void DecLoad()
    {
        if (_mtx)
            _mtx->lock();
            
        _load--;

        if (_mtx)
            _mtx->unlock();
    }

    uint64_t Load()
    {
        uint64_t load = 0;
        if (_mtx)
        _mtx->lock();
        load = _load;
        if (_mtx)
        _mtx->unlock();

        return load;
    }
};

const std::string machine_path = "./conf/service_machine.conf";
// 负载均衡模块
class LoadBalance
{
private:
    // 下标充当主机编号
    std::vector<Machine> _mchs;
    // 在线的主机，通过下标找到主机信息
    std::vector<int> _onlines;
    std::vector<int> _offlines;
    // 保证安全
    std::mutex _mtx;
public:
    LoadBalance()
    {
        assert(LoadConf(machine_path));
        LOG(INFO) << "加载主机列表成功\n";
    }
    ~LoadBalance()
    {}

    bool LoadConf(const std::string& machine_list)
    {
        std::ifstream in(machine_list);
        if (!in.is_open())
        {
            LOG(FATAL) << "加载主机列表失败\n";
            return false;
        }

        std::string line;
        while (std::getline(in, line))
        {

            std::vector<std::string> tokens;
            StringUtil::SplitString(line, &tokens, ":");
            if (tokens.size() != 2)
            {
                LOG(WARNNING) << "切分失败\n";
                continue;
            }

            Machine mache;
            mache._ip = tokens[0];
            mache._port = atoi(tokens[1].c_str());
            mache._load = 0;
            mache._mtx = new std::mutex();
            _onlines.push_back(_mchs.size());
            _mchs.push_back(mache);
        }

        in.close();
        return true;
    }

    // 选择负载最小的主机，更新负载
    // 离线主机
    bool SmartChoice(int* id, Machine** m)
    {
        _mtx.lock();
        int online_num = _onlines.size();
        if (online_num == 0)
        {
            _mtx.unlock();
            LOG(FATAL) << "所有主机都已离线，尽快查看问题\n";
            return false;
        }

        *id = _onlines[0];
        *m = &_mchs[_onlines[0]];
        uint64_t min_load = _mchs[_onlines[0]].Load();
        for (int i = 0; i < online_num; i++)
        {
            uint64_t cur_load = _mchs[_onlines[i]].Load();
            if (min_load > cur_load)
            {
                min_load = cur_load;
                *id = _onlines[i];
                *m = &_mchs[_onlines[i]];
            }
        }
        
        _mtx.unlock();
        return true; 
    }

    void OnlineMachine()
    {
        _mtx.lock();
        //统一上线，后面统一解决
        _onlines.insert(_onlines.end(), _offlines.begin(), _offlines.end());
        _offlines.erase(_offlines.begin(), _offlines.end());
        _mtx.unlock();
    }

    void OfflineMachine(int which)
    {
        _mtx.lock();
        for (auto it = _onlines.begin(); it != _onlines.end(); it++)
        {
            if (*it == which)
            {
                _onlines.erase(it);
                _offlines.push_back(which);
                break; //因为break的存在，所有我们暂时不考虑迭代器失效的问题
            }
        }
        _mtx.unlock();
    }

    void ShowMachines()
    {
        _mtx.lock();
        std::cout << "当前在线主机列表: ";
        for(auto &id : _onlines)
        {
            std::cout << id << " ";
        }
        std::cout << std::endl;
        std::cout << "当前离线主机列表: ";
        for(auto &id : _offlines)
        {
            std::cout << id << " ";
        }
        std::cout << std::endl;
        _mtx.unlock();

    }
};
class Control
{
private:
    Model _model;
    View _view;
    LoadBalance _load;
public:
    bool AllQuestions(std::string* html)
    {
        std::vector<struct Question> all;
        if (_model.GetAllQuestions(&all))
        {
            std::vector<struct Question> all;
            if (_model.GetAllQuestions(&all))
            {
                sort(all.begin(), all.end(),[](const struct Question &q1, const struct Question &q2)
                {
                    return atoi(q1._num.c_str()) < atoi(q2._num.c_str());
                });
            }
            _view.AllExpandHtml(all, html);
        }
        else
        {
            *html = "获取题库失败";
            return false;
        }

        return true;
    }

    bool Question(const std::string num, std::string* html)
    {
        struct Question q;
        if (_model.GetOneQuestion(num, &q))
        {
            _view.OneExpandHtml(q, html);
        }
        else
        {
            *html = "指定题目"+num+"不存在";
            return false;
        }
    }

    void Judge(const std::string &number, const std::string in_json, std::string *out_json)
    {
        // LOG(DEBUG) << in_json << " \nnumber:" << number << "\n";
        
        // 0. 根据题目编号，直接拿到对应的题目细节
        struct Question q;
        _model.GetOneQuestion(number, &q);

        // 1. in_json进行反序列化，得到题目的id，得到用户提交源代码，input
        Json::Reader reader;
        Json::Value in_value;
        reader.parse(in_json, in_value);
        std::string code = in_value["code"].asString();

        // 2. 重新拼接用户代码+测试用例代码，形成新的代码
        Json::Value compile_value;
        compile_value["input"] = in_value["input"].asString();
        compile_value["code"] = code + "\n" + q._tail;
        compile_value["cpu_limit"] = q._cpu_limit;
        compile_value["mem_limit"] = q._mem_limit;
        Json::FastWriter writer;
        std::string compile_string = writer.write(compile_value);

        // 3. 选择负载最低的主机(差错处理)
        // 规则: 一直选择，直到主机可用，否则，就是全部挂掉
        while(true)
        {
            int id = 0;
            Machine *m = nullptr;
            if(!_load.SmartChoice(&id, &m))
            {
                break;
            }

            // 4. 然后发起http请求，得到结果
            Client cli(m->_ip, m->_port);
            m->IncLoad();
            LOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << m->_ip << ":" << m->_port << " 当前主机的负载是: " << m->Load() << "\n";
            if(auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))
            {
                // 5. 将结果赋值给out_json
                if(res->status == 200)
                {
                    *out_json = res->body;
                    m->DecLoad();
                    LOG(INFO) << "请求编译和运行服务成功..." << "\n";
                    break;
                }
                m->DecLoad();
                std::cout << "res:" << res->status << std::endl;
            }
            else
            {
                // 请求失败
                LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->_ip << ":" << m->_port << " 可能已经离线" << "\n";
                _load.OfflineMachine(id);
                _load.ShowMachines(); // 仅仅是为了用来调试
            }
        }
    }


    void RecoveryMachine()
    {
        _load.OnlineMachine();
    }
};